﻿// <copyright file="NeptuneDBActualRainSource.cs" company="City of Portland, BES-ASM">
// </copyright>
// <summary>NeptuneDBActualRainSource class</summary>

namespace BESASM.EMGAATS.DataAccess
{
  using System;
  using System.Collections.Generic;
  using System.Data.SqlClient;
  using System.Linq;
  using System.Text;
  using System.Threading.Tasks;
  using BusinessClasses;

  /// <summary>
  /// Manages a Neptune SQL Server database rain source
  /// </summary>
  public class NeptuneDBActualRainSource : IRainSource, IDisposable
  {
    private const string SQLDateFormat = "yyyy-MM-ddTHH:mm:ss";
    private DateTime startDateTime;
    private DateTime endDateTime;
    private int timeStepSec;

    private Dictionary<int, IPoint> raingages;

    private string connectionString = "SERVER=BESDBPROD2;DATABASE=NEPTUNE;Trusted_Connection=Yes";
    private SqlConnection conn;

    /// <summary>
    /// Initializes a new instance of the <see cref="NeptuneDBActualRainSource"/> class
    /// </summary>
    /// <param name="startDateTime">The starting date/time of the storm</param>
    /// <param name="endDateTime">The ending date/time of the storm</param>
    /// <param name="timeStepSec">The desired time step in seconds</param>
    public NeptuneDBActualRainSource(DateTime startDateTime, DateTime endDateTime, int timeStepSec)
    {
      int[] validTimeSteps = new int[] { 60, 300, 900, 3600 };

      if (!validTimeSteps.Contains(timeStepSec))
      {
        throw new ArgumentException(
          string.Format(
            "{0} is an invalid time step; 60, 300, 900, and 3600 seconds are allowed.",
            timeStepSec));
      }

      this.startDateTime = startDateTime;
      this.endDateTime = endDateTime;
      this.timeStepSec = timeStepSec;

      this.conn = new SqlConnection(this.connectionString);
      this.conn.Open();
    }

    /// <summary>
    /// Gets a list of the raingauges and their coordinates
    /// </summary>
    public Dictionary<int, IPoint> Locations
    {
      get
      {
        if (this.raingages == null)
        {
          this.FillRaingages();
        }

        return this.raingages;
      }
    }

    /// <summary>
    /// Gets a list of the raingauges
    /// </summary>
    public List<int> LocationIDs
    {
      get 
      {
        if (this.raingages == null)
        {
          this.FillRaingages();
        }

        var l = from r in this.raingages
                orderby r.Key
                select r.Key;

        return l.ToList();
      }
    }

    /// <summary>
    /// Gets the enumeration of hyetograph points for the raingage
    /// </summary>
    /// <param name="raingage">The raingage ID of interest</param>
    /// <returns>The enumeration of hyetograph points</returns>
    public IEnumerable<HyetographPoint> Rainfall(int raingage)
    {
      string timestepString;
      switch (this.timeStepSec)
      {
        case 60:
          timestepString = "@interval=1, @daypart='minute'";
          break;
        case 300:
          timestepString = "@interval=5, @daypart='minute'";
          break;
        case 900:
          timestepString = "@interval=15, @daypart='minute'";
          break;
        case 3600:
          timestepString = "@interval=1, @daypart='day'";
          break;
        default:
          timestepString = "@interval=5, @daypart='minute'";
          break;
      }

      string commandString = string.Format(
        "EXEC USP_MODEL_RAIN @start_date='{0}', @end_date='{1}', {2}, @h2_number={3}",
        this.startDateTime.ToString(SQLDateFormat),
        this.endDateTime.ToString(SQLDateFormat),
        timestepString,
        raingage);
      SqlCommand getRainfall = new SqlCommand(commandString, this.conn);
      using (SqlDataReader reader = getRainfall.ExecuteReader())
      {
        while (reader.Read())
        {
          double rainfallDepthIn;
          double rainfallIntensityInPerHr;
          try
          {
            rainfallDepthIn = Convert.ToDouble(reader.GetDecimal(5));
            rainfallIntensityInPerHr = rainfallDepthIn / ((this.timeStepSec / 60.0) / 60.0);
          }
          catch (System.Data.SqlTypes.SqlNullValueException)
          {
            throw new ArgumentException(string.Format("Raingauge {0} does not exist for the time period", raingage));
          }

          yield return new HyetographPoint(reader.GetDateTime(3), rainfallIntensityInPerHr);
        }
      }
    }

    /// <summary>
    /// Disposes the object
    /// </summary>
    public void Dispose()
    {
      this.conn.Close();
    }

    private void FillRaingages()
    {
      SqlCommand getRaingagesCommand = new SqlCommand(
                  "SELECT h2_number, state_plane_x_ft, state_plane_y_ft " +
                  "FROM RAIN_SENSOR INNER JOIN STATION ON STATION.station_id = RAIN_SENSOR.station_id " +
                  "WHERE RAIN_SENSOR.start_date <= '" + this.startDateTime.ToString(SQLDateFormat) + "' AND " +
                  "(RAIN_SENSOR.end_date >= '" + this.endDateTime.ToString(SQLDateFormat) + "' OR RAIN_SENSOR.end_date IS NULL) " +
                  "ORDER BY h2_number",
                  this.conn);
      SqlDataReader reader = getRaingagesCommand.ExecuteReader();

      this.raingages = new Dictionary<int, IPoint>();
      while (reader.Read())
      {
        this.raingages.Add(
          reader.GetInt32(0),
          new RaingageLocation(
            Convert.ToDouble(reader.GetInt32(1)),
            Convert.ToDouble(reader.GetInt32(2))));
      }
    }
  }
}
